package com.hellobike.haxing.util;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.hellobike.haxing.exception.LockOwnerException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class RedisUtil {

    @Autowired
    public RedisTemplate redisTemplate;

    @Autowired
    public RetryTemplate retryTemplate;

    public <T> T getObject(Class<T> clazz, String key) {
        String val = String.valueOf(redisTemplate.opsForValue().get(key));
        if (StringUtils.isEmpty(val)) {
            return null;
        } else {
            return JSON.parseObject(val, clazz);
        }
    }

    /**
     * 获取过期时间
     *
     * @param key
     * @return
     */
    public Long ttl(String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 设置过期时间
     *
     * @param key
     * @param expire
     * @param timeUnit
     * @return
     */
    public boolean expire(String key, Long expire, TimeUnit timeUnit) {
        try {
            int result = retryTemplate.execute(new RetryCallback<Integer, Throwable>() {
                int count = 0;

                @Override
                public Integer doWithRetry(RetryContext context) throws Throwable {
                    redisTemplate.expire(key, expire, timeUnit);
                    return ++count;
                }
            });
            return true;
        } catch (Throwable t) {
            log.error("", t);
            return false;
        }
    }

    public <T> List<T> getList(Class<T> clazz, String key) {
        String val = String.valueOf(redisTemplate.opsForValue().get(key));
        if (StringUtils.isEmpty(val)) {
            return null;
        } else {
            return JSON.parseObject(val, new TypeReference<List<T>>() {
            });
        }
    }

    public boolean setObject(String key, Object obj, Integer expire, TimeUnit timeUnit) {
        try {
            int result = retryTemplate.execute(new RetryCallback<Integer, Throwable>() {
                int count = 0;

                @Override
                public Integer doWithRetry(RetryContext context) throws Throwable {
                    redisTemplate.opsForValue().set(key, JSON.toJSONString(obj), expire, timeUnit);
                    return ++count;
                }
            });
            return true;
        } catch (Throwable t) {
            log.error("", t);
            return false;
        }
    }

    public <T> T parsePrimitive(Number number, Class<T> type) {
        if (number.getClass().equals(type)) {
            return (T) number;
        }
        if (type.equals(Integer.class)) {
            return (T) Integer.valueOf(number.intValue());
        } else if (type.equals(Long.class)) {
            return (T) Long.valueOf(number.longValue());
        } else if (type.equals(Double.class)) {
            return (T) Double.valueOf(number.doubleValue());
        } else if (type.equals(BigDecimal.class)) {
            return (T) BigDecimal.valueOf(number.doubleValue());
        }
        return null;
    }

    public <T> T getVal(Class<T> type, String key) {
        Object val = redisTemplate.opsForValue().get(key);
        if (val != null) {
            if (val instanceof Number) {
                return parsePrimitive((Number) val, type);
            } else {
                return type.cast(val);
            }
        } else {
            return null;
        }
    }

    public boolean setVal(String key, Object val, Integer expire, TimeUnit timeUnit) {
        try {
            retryTemplate.execute(new RetryCallback<Integer, Throwable>() {
                int count = 0;

                @Override
                public Integer doWithRetry(RetryContext context) throws Throwable {
                    redisTemplate.opsForValue().set(key, val, expire, timeUnit);
                    return ++count;
                }
            });
            return true;
        } catch (Throwable t) {
            log.error("", t);
            return false;
        }
    }

    public boolean lock(String key, String lockId, Integer lockTime, TimeUnit timeUnit) {
        /**
         * 如果过期时间不存在，表示该锁有问题，直接删除
         */
        if (redisTemplate.getExpire(key) == -1) {
            redisTemplate.delete(key);
        }
        return redisTemplate.opsForValue().setIfAbsent("locked:" + key, lockId, lockTime, timeUnit);
    }

    public boolean unlock(String key, String lockId) throws Exception {
        if (lockId.equalsIgnoreCase(getVal(String.class, key))) {
            redisTemplate.delete(key);
            return true;
        } else {
            throw new LockOwnerException();
        }
    }

    public boolean delete(String... keys) {
        try {
            retryTemplate.execute(new RetryCallback<Object, Throwable>() {
                @Override
                public Object doWithRetry(RetryContext context) throws Throwable {
                    redisTemplate.delete(keys);
                    return null;
                }
            });
            return true;
        } catch (Throwable t) {
            log.error("", t);
            return false;
        }
    }

    public void hset(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }

    public <T> T hget(String key, String field, Class<T> clazz) {
        return clazz.cast(redisTemplate.opsForHash().get(key, field));
    }

    public void hdel(String key, Object... fields) {
        redisTemplate.opsForHash().delete(key, fields);
    }

    public Map<Object, Object> hgetall(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    public long lpush(String key, Object data) {
        return redisTemplate.opsForList().leftPush(key, data);
    }

    public <T> T lpop(String key, Class<T> clazz) {
        return clazz.cast(redisTemplate.opsForList().leftPop(key));
    }

    public long rpush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

}
